﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.Collections;
using System.Linq;

namespace SoundCatcher
{
    public enum DrawMethod { Time, Freqency, Spectrogram }
    public class Spectrogram : PictureBox, IObserver<IEnumerable<double>>
    {
        public DrawMethod GraphType { get; set; }
        public int SampleRate { get; set; }

        private int _maxHeightSpect = 0;
        private List<IEnumerable<double>> Spectrum = new List<IEnumerable<double>>();

        private void DrawSignal(DrawMethod drawmethod, IEnumerable<double> Samples, int SampleRate)
        {
            switch (drawmethod)
            {
                case DrawMethod.Time:
                    DrawTime(Samples);
                    break;
                case DrawMethod.Freqency:
                    DrawFrequency(Samples, SampleRate);
                    break;
                case DrawMethod.Spectrogram:
                    DrawSpectogram(Samples);
                    break;
                default:
                    DrawTime(Samples);
                    break;
            }
        }

        private void DrawTime(IEnumerable<double> Samples)
        {
            // Set up for drawing
            Bitmap canvas = new Bitmap(this.Width, this.Height);
            Graphics offScreenDC = Graphics.FromImage(canvas);
            Pen pen = new System.Drawing.Pen(Color.WhiteSmoke);

            // Determine channnel boundries
            int width = canvas.Width;
            int height = canvas.Height;
            double center = height / 2;

            // Draw left channel
            double scale = 0.5f * height / 32768;  // a 16 bit sample has values from -32768 to 32767
            int xPrev = 0, yPrev = 0;
            for (int x = 0; x < width; x++)
            {
                int y = (int)(center + (Samples.ElementAt(Samples.Count() / width * x) * scale));
                if (x == 0)
                {
                    xPrev = 0;
                    yPrev = y;
                }
                else
                {
                    pen.Color = Color.Green;
                    offScreenDC.DrawLine(pen, xPrev, yPrev, x, y);
                    xPrev = x;
                    yPrev = y;
                }
            }

            // Clean up
            this.Image = canvas;
            offScreenDC.Dispose();
        }

        private void DrawSpectogram(IEnumerable<double> Samples)
        {
            Spectrum.Add(Samples);
            Bitmap canvas = new Bitmap(this.Width, this.Height);
            Graphics offScreenDC = Graphics.FromImage(canvas);

            // Determine channnel boundries
            int width = canvas.Width;
            int height = canvas.Height;

            double min = double.MaxValue;
            double max = double.MinValue;
            double range = 0;

            if (height < _maxHeightSpect)
                Spectrum.Clear();

            _maxHeightSpect = height;

            if (Spectrum.Count > _maxHeightSpect)
                Spectrum.RemoveAt(0);


            // get min/max
            foreach (var row in Spectrum)
            {
                foreach (var val in row)
                {
                    if (min > val)
                    {
                        min = val;
                    }
                    if (max < val)
                    {
                        max = val;
                    }
                }
            }

            // get range
            if (min < 0 || max < 0)
                if (min < 0 && max < 0)
                    range = max - min;
                else
                    range = Math.Abs(min) + max;
            else
                range = max - min;

            // lock image
            PixelFormat format = canvas.PixelFormat;
            BitmapData data = canvas.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, format);
            int stride = data.Stride;
            int offset = stride - width * 4;

            try
            {
                unsafe
                {
                    byte* pixel = (byte*)data.Scan0.ToPointer();
                    int lines = height > Spectrum.Count ? Spectrum.Count : height;
                    // for each cloumn
                    for (int y = 0; y < lines; y++)
                    {
                        //if (y < Spectrum.Count)
                        //{
                            // for each row
                            for (int x = 0; x < width; x++, pixel += 4)
                            {
                                double amplitude = ((double[])Spectrum[Spectrum.Count - y - 1])[(int)(((double)(Samples.Count()) / (double)(width)) * x)];
                                byte color = (byte)GetColor(min, max, range, amplitude);
                                pixel[0] = (byte)(color + 80);
                                pixel[1] = (byte)0;
                                pixel[2] = (byte) color;
                                pixel[3] = (byte)255;
                            }
                            pixel += offset;
                        //}
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            // unlock image
            canvas.UnlockBits(data);

            // Clean up
            this.Image = canvas;
            offScreenDC.Dispose();
        }

        private void DrawFrequency(IEnumerable<double> Spectrum, int SampleRate)
        {
            // Set up for drawing
            Bitmap canvas = new Bitmap(this.Width, this.Height);
            Graphics offScreenDC = Graphics.FromImage(canvas);
            SolidBrush brush = new System.Drawing.SolidBrush(Color.FromArgb(128, 255, 255, 255));
            Pen pen = new System.Drawing.Pen(Color.WhiteSmoke);
            Font font = new Font("Arial", 10);

            // Determine channnel boundries
            int width = canvas.Width;
            int height = canvas.Height;

            double min = double.MaxValue;
            double minHz = 0;
            double max = double.MinValue;
            double maxHz = 0;
            double range = 0;
            double scale = 0;
            double scaleHz = (double)(SampleRate / 2) / (double)Spectrum.Count();

            // get left min/max
            Spectrum.Max();
           
            min = Spectrum.Min();
            minHz = (double)min * scaleHz;

            max = Spectrum.Max();
            maxHz = (double)max * scaleHz;

            range = Math.Abs(max) - min;
            scale = range / height;

            // draw right channel
            for (int xAxis = 0; xAxis < width; xAxis++)
            {
                double amplitude = (double)Spectrum.ElementAt((int)(((double)(Spectrum.Count()) / (double)(width)) * xAxis));
                if (amplitude == double.NegativeInfinity || amplitude == double.PositiveInfinity || amplitude == double.MinValue || amplitude == double.MaxValue)
                    amplitude = 0;
                int yAxis;
                if (amplitude < 0)
                    yAxis = (int)(height - ((amplitude - min) / scale));
                else
                    yAxis = (int)(0 + ((max - amplitude) / scale));
                if (yAxis < 0)
                    yAxis = 0;
                if (yAxis > height)
                    yAxis = height;
                pen.Color = pen.Color = Color.FromArgb(0, GetColor(min, max, range, amplitude), 0);
                offScreenDC.DrawLine(pen, xAxis, height, xAxis, yAxis);
            }
            offScreenDC.DrawString("Min: " + minHz.ToString(".#") + " Hz (±" + scaleHz.ToString(".#") + ") = " + min.ToString(".###") + " dB", font, brush, 0 + 1, 0 + 1);
            offScreenDC.DrawString("Max: " + maxHz.ToString(".#") + " Hz (±" + scaleHz.ToString(".#") + ") = " + max.ToString(".###") + " dB", font, brush, 0 + 1, 0 + 18);

            // Clean up
            this.Image = canvas;
            offScreenDC.Dispose();
        }


        /// <summary>
        /// Override this method to have custom colours & color scale
        /// </summary>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <param name="range"></param>
        /// <param name="amplitude"></param>
        /// <returns></returns>
        protected virtual int GetColor(double min, double max, double range, double amplitude)
        {
            double color;
            if (min != double.NegativeInfinity && min != double.MaxValue & max != double.PositiveInfinity && max != double.MinValue && range != 0)
            {
                if (min < 0 || max < 0)
                    if (min < 0 && max < 0)
                        color = (255 / range) * (Math.Abs(min) - Math.Abs(amplitude));
                    else
                        if (amplitude < 0)
                            color = (255 / range) * (Math.Abs(min) - Math.Abs(amplitude));
                        else
                            color = (255 / range) * (amplitude + Math.Abs(min));
                else
                    color = (255 / range) * (amplitude - min);
            }
            else
                color = 0;
            return (int)((color/255)*175);
        }


        public void OnCompleted()
        {
            throw new NotImplementedException();
        }

        public void OnError(Exception error)
        {
            throw new NotImplementedException();
        }

        public void OnNext(IEnumerable<double> value)
        {
            DrawSignal(GraphType, value, SampleRate);
        }
    }
}
